<?php
/* xml-simple.php

Simple XML Parser for PHP by Rogers Cadenhead, derived from
original code by Jim Winstead Jr.

Version 1.01
Web: http://www.cadenhead.org/workbench/xml-simple
   
Copyright (C) 2005 Rogers Cadenhead

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */

if (__FILE__ == $PATH_TRANSLATED) {
    header("Status: 403 Forbidden");
    exit();
}

// a PHP class library that parses XML data
class Xml
{
    /* 
     * An array that holds parsed XML data as either name-value pairs (for character data) or
     * arrays (for subelements)
     */
    var $tree = array();
    var $force_to_array = array();

    // a descriptive error message, if the class fails to execute successfully
    var $error = null;

	/**
	 * Singleton Pattern.
	 *
	 * @var object
	 **/
	private static $instance = NULL;

	// --------------------------------------------------------------------

	/**
	 * Implemento il Singleton.
	 *
	 * @return	object
	 */
	public static function get_instance()
	{
		if ( self::$instance == NULL )
		{   
			$c = __CLASS__;
		    self::$instance =& new $c;
		}
		
		return self::$instance;
	}

	// --------------------------------------------------------------------

	private function __construct()
	{
        $this->parser = xml_parser_create(App::setting('Enviroments.default_charset'));

		// Comment con php5.
		//xml_set_object($this->parser, &$this);

        xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
        xml_parser_set_option($this->parser, XML_OPTION_SKIP_WHITE, 1);
        xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');

        xml_set_element_handler($this->parser, "start_element", "stop_element");

        xml_set_character_data_handler($this->parser, "char_data");
	}
	
	// --------------------------------------------------------------------

	private function __clone(){}
	
	// --------------------------------------------------------------------

	public function __destruct(){}
	
	// --------------------------------------------------------------------

	/**
	 * Implemento il Factory.
	 */
	public static function factory($file)
	{
		$xml = self::get_instance();
		return $xml->parse_file($file);
	}

	// --------------------------------------------------------------------

    function force_to_array() {
        for ($i = 0; $i < func_num_args(); $i++) {
            $this->force_to_array[] = func_get_arg($i);
        }
    }

    /**
     * Parse XML data, storing it in the instance variable;
     * Returns false if the data cannot be parsed.
     *
     * @return boolean
     */
    function parse($data) {
        $this->tree = array();

        if (!xml_parse($this->parser, $data, 1)) {
            $this->error = "xml parse error: " .
                xml_error_string(xml_get_error_code($this->parser)) .
	            " on line ".xml_get_current_line_number($this->parser);
            return false;
        }
        return $this->tree[0]["content"];
    }

    function parse_file($file) {
        $fp = @fopen($file, "r");
        if (!$fp) {
            user_error("unable to open file: '$file'");
            return false;
        }   
        while ($data = fread($fp, 4096)) {
            if (!xml_parse($this->parser, $data, feof($fp))) {
	            user_error("xml parse error: " .
	                xml_error_string(xml_get_error_code($this->parser)) .
	                " on line " . xml_get_current_line_number($this->parser));
                }
            }
            fclose($fp);
            return $this->tree[0]["content"];
        }

    function encode_as_xml($value) {
        if (is_array($value)) {
            reset($value); $out = '';
            while (list($key,$val) = each($value)) {
	            if (is_array($val) && isset($val[0])) {
	                reset($val);
	                while (list(,$item) = each($val)) {
	                    $out .= "<$key>".xml_simple::encode_as_xml($item)."</$key>";
	                }
	            } else {
	                $out .= "<$key>".xml_simple::encode_as_xml($val)."</$key>";
	            }
            }
            return $out;
        } else {
            return htmlspecialchars($value);
        }
    }

    function start_element($parser, $name, $attrs) {
        array_unshift($this->tree, array("name" => $name));
    }

    function stop_element($parser, $name) {
        if ($name != $this->tree[0]["name"]) die("incorrect nesting");
        if (count($this->tree) > 1) {
            $elem = array_shift($this->tree);
            if (isset($this->tree[0]["content"][$elem["name"]])) {
	            if (is_array($this->tree[0]["content"][$elem["name"]]) && isset($this->tree[0]["content"][$elem["name"]][0])) {
	                array_push($this->tree[0]["content"][$elem["name"]], $elem["content"]);
	            } else {
	                $this->tree[0]["content"][$elem["name"]] =
	                array($this->tree[0]["content"][$elem["name"]],$elem["content"]);
	            }
            } else {
	            if (in_array($elem["name"],$this->force_to_array)) {
	                $this->tree[0]["content"][$elem["name"]] = array($elem["content"]);
	            } else {
	                if (!isset($elem["content"])) $elem["content"] = "";
	                $this->tree[0]["content"][$elem["name"]] = $elem["content"];
	            }
            }
        }
    }

    function char_data($parser, $data) {
        # don't add a string to non-string data
        if (!is_string($this->tree[0]["content"]) && !preg_match("/\\S/", $data)) return;
        $this->tree[0]["content"] .= $data;
    }
}
